﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Transactions;
using System.Xml.Linq;
using Common.DLR;
using DAL.Workflows;
using DO.Workflows;

namespace BLL.Workflows
{
    public class WorkflowManager
    {
        #region workflows
        public static WorkflowDesign GetWorkflow(Guid workflowID)
        {
            return WorkflowDesignDALC.GetWorkflow(workflowID);
        }

        public static WorkflowDesign GetWorkflowByName(string workflowName)
        {
            return WorkflowDesignDALC.GetWorkflowByName(workflowName);
        }

        public static WorkflowDesign GetWorkflowByRun(Guid runID)
        {
            return WorkflowDesignDALC.GetWorkflowByRun(runID);
        }

        public static List<WorkflowDesign> GetAllWorkflows()
        {
            return WorkflowDesignDALC.GetAllWorkflows();
        }

        public static void SaveWorkflow(WorkflowDesign wfDesign)
        {
            WorkflowDesignDALC.SaveWorkflow(wfDesign);
            foreach (WorkflowComponentBase component in wfDesign.Components.Values)
            {
                ComponentReflector componentReflector =
                    new ComponentReflector
                        {
                            ComponentID = component.ComponentID,
                            AssemblyFileName = component.GetType().Assembly.GetName().Name,
                            ComponentTypeName = component.GetType().Name,
                            WorkflowID = wfDesign.WorkflowID
                        };
                ComponentDALC.InsertComponent(componentReflector);
            }
            WorkflowTrigger trigger = WorkflowManager.GetTrigger(wfDesign);
            if(trigger !=null)
            {
                if(WorkflowTriggerDALC.TriggerExistInWorkflow(trigger.ComponentID, wfDesign.WorkflowID))
                {
                    ((WorkflowTriggerInstructionBase) trigger.Instruction).LastRunTime = null;
                    ((WorkflowTriggerInstructionBase)trigger.Instruction).NextRunTime = null;
                    ((WorkflowTriggerInstructionBase)trigger.Instruction).ActivatedOn = DateTime.Now;
                    WorkflowTriggerDALC.ResetWorkflowTrigger(trigger);
                }
                else
                {
                    WorkflowTriggerDALC.InsertWorkflowTrigger(wfDesign.WorkflowID, trigger);
                }
            }
        }

        public static void PublishWorkflow(string xml)
        {
            WorkflowDesignDALC.PublishWorkflow(xml);
        }

        public static List<string> ListWorkflowNames()
        {
            return WorkflowDesignDALC.ListWorkflowNames();
        }

        public static void DeleteWorkflowByName(string workflowName)
        {
            WorkflowDesignDALC.DeleteWorkflowByName(workflowName);
        }

        public static void DeleteWorkflow(Guid workflowID)
        {
            WorkflowDesignDALC.DeleteWorkflow(workflowID);
        }
        #endregion

        #region trigger
        public static WorkflowTrigger GetTrigger(WorkflowDesign wfDesign)
        {
            int triggerCount = 0;
            WorkflowTrigger trigger = null;
            foreach (WorkflowComponentBase component in wfDesign.Components.Values)
            {
                if (component.GetType().IsSubclassOf(typeof (WorkflowTrigger)))
                {
                    trigger = (WorkflowTrigger) component;
                    triggerCount++;
                }
            }
            if (triggerCount == 1)
                return trigger;
            else if (triggerCount > 1)
                throw new Exception("Workflow can only have one trigger");
            else
                return null;
        }

        public static void ResetWorkflowTrigger(WorkflowTrigger trigger)
        {
            WorkflowTriggerDALC.ResetWorkflowTrigger(trigger);
        }

        public static WorkflowTrigger GetWorkflowTrigger(Guid componentID)
        {
            WorkflowTriggerInstructionBase triggerInstructionBase =
                WorkflowTriggerDALC.GetWorkflowTrigger(componentID);
            WorkflowDesign wfDesign = WorkflowDesignDALC.GetWorkflowByComponent(componentID);
            WorkflowComponentBase component = wfDesign.Components[componentID];
            if(component.GetType().IsSubclassOf(typeof(WorkflowTrigger)))
            {
                ((WorkflowTriggerInstructionBase) component.Instruction).TriggerType =
                    triggerInstructionBase.TriggerType;
                ((WorkflowTriggerInstructionBase)component.Instruction).ActivatedOn =
                    triggerInstructionBase.ActivatedOn;
                ((WorkflowTriggerInstructionBase)component.Instruction).IsActive =
                    triggerInstructionBase.IsActive;
                ((WorkflowTriggerInstructionBase)component.Instruction).LastRunTime =
                    triggerInstructionBase.LastRunTime;
                ((WorkflowTriggerInstructionBase)component.Instruction).NextRunTime =
                    triggerInstructionBase.NextRunTime;
                ((WorkflowTriggerInstructionBase)component.Instruction).Span =
                    triggerInstructionBase.Span;
                ((WorkflowTriggerInstructionBase)component.Instruction).SubmitTime =
                    triggerInstructionBase.SubmitTime;
                return (WorkflowTrigger) component;
            }
            else 
                throw new Exception("Component is not trigger");
        }

        public static WorkflowTrigger GetWorkflowTriggerByWorkflow(WorkflowDesign wfDesign)
        {
            WorkflowTriggerInstructionBase triggerInstructionBase =
                WorkflowTriggerDALC.GetWorkflowTriggerByWorkflow(wfDesign.WorkflowID);
            WorkflowTrigger trigger = WorkflowManager.GetTrigger(wfDesign);

            if (trigger!=null)
            {
                ((WorkflowTriggerInstructionBase)trigger.Instruction).TriggerType =
                    triggerInstructionBase.TriggerType;
                ((WorkflowTriggerInstructionBase)trigger.Instruction).ActivatedOn =
                    triggerInstructionBase.ActivatedOn;
                ((WorkflowTriggerInstructionBase)trigger.Instruction).IsActive =
                    triggerInstructionBase.IsActive;
                ((WorkflowTriggerInstructionBase)trigger.Instruction).LastRunTime =
                    triggerInstructionBase.LastRunTime;
                ((WorkflowTriggerInstructionBase)trigger.Instruction).NextRunTime =
                    triggerInstructionBase.NextRunTime;
                ((WorkflowTriggerInstructionBase)trigger.Instruction).Span =
                    triggerInstructionBase.Span;
                ((WorkflowTriggerInstructionBase)trigger.Instruction).SubmitTime =
                    triggerInstructionBase.SubmitTime;
                return trigger;
            }
            else
                throw new Exception("Component is not trigger");
        }
        #endregion

        #region component
        public static WorkflowComponentBase GetComponent(Guid componentID)
        {
            WorkflowDesign wfDesign = WorkflowDesignDALC.GetWorkflowByComponent(componentID);
            return wfDesign.Components[componentID];
        }
        #endregion

        #region run/job
        public static void AddRun(Run run)
        {
            RunDALC.AddRun(run);
        }

        public static void SaveRun(Run run)
        {
            RunDALC.SaveRun(run);
        }

        public static List<Run> GetWorkflowRuns(Guid workflowID)
        {
            return RunDALC.GetWorkflowRuns(workflowID);
        }

        public static Run GetRunByJob(Guid jobID)
        {
            return RunDALC.GetRunByJob(jobID);
        }

        public static void AddJob(Job job)
        {
            JobDALC.AddJob(job);
        }

        public static void SaveJob(Job job)
        {
            JobDALC.SaveJob(job);
        }

        public static Job GetJob(Guid jobID)
        {
            return JobDALC.GetJob(jobID);
        }

        public static List<Job> CheckOutJobs(List<string> handlableComponentNames)
        {
            List<Job> jobs = JobDALC.GetAllQueuedJobs();
            List<Job> handlableJobs = new List<Job>();
            
            using (TransactionScope ts = new TransactionScope())
            {
                foreach (Job job in jobs)
                {
                    ComponentReflector componentReflector = ComponentDALC.GetComponent(job.ComponentID);
                    bool upstreamJobsDone = JobDALC.AreUpstreamJobsDone(job);
                    if (upstreamJobsDone && componentReflector != null)
                    {
                        string componentTypeName = componentReflector.ComponentTypeName;
                        if (componentTypeName.IndexOf(".") > 0)
                            componentTypeName = componentTypeName.Substring(componentTypeName.LastIndexOf(".") + 1);
                        if (handlableComponentNames.Contains(componentTypeName))
                        {
                            job.ReceiveTime = DateTime.Now;
                            JobDALC.ReceiveJob(job.JobID, job.ReceiveTime, Environment.MachineName, Environment.UserName);
                            if(job.IsFirstJob)
                            {
                                Run run = RunDALC.GetRunByJob(job.JobID);
                                if(run.ReceiveTime==null)
                                {
                                    run.ReceiveTime = DateTime.Now;
                                    RunDALC.SaveRun(run);
                                }
                            }
                            handlableJobs.Add(job);
                        }
                    }
                }
                ts.Complete();
            }

            return handlableJobs;
        }

        public static void SubmitJob(Guid jobID, DateTime? submitTime)
        {
            JobDALC.SubmitJob(jobID, submitTime, Environment.MachineName, Environment.UserName);
        }

        public static List<Guid> DisableJob(Guid jobID)
        {
            List<Guid> disabledJobIDs=new List<Guid>();
            JobDALC.DisableJob(jobID);
            disabledJobIDs.Add(jobID);
            Job job = JobDALC.GetJob(jobID);
            WorkflowDesign wfDesign = WorkflowDesignDALC.GetWorkflowByComponent(job.ComponentID);
            List<Guid> dependentComponentIDs = GetDependentComponentIDs(wfDesign, job.ComponentID);
            foreach(Guid dependentComponentID in dependentComponentIDs)
            {
                Job dependentJob = JobDALC.GetJobByComponent(dependentComponentID, job.RunID);
                if(!disabledJobIDs.Contains(dependentJob.JobID))
                {
                    JobDALC.DisableJob(dependentJob.JobID);
                    disabledJobIDs.Add(dependentJob.JobID);
                }
            }
            return disabledJobIDs;
        }

        private static List<Guid> GetDependentComponentIDs(WorkflowDesign wfDesign, Guid componentID)
        {
            List<Guid> downstreamIDs=new List<Guid>();
            if(wfDesign.SuccessConnectors!=null && wfDesign.SuccessConnectors.ContainsKey(componentID))
            {
                foreach(Guid toID in wfDesign.SuccessConnectors[componentID])
                {
                    if(!downstreamIDs.Contains(toID))
                    {
                        downstreamIDs.Add(toID);
                        List<Guid> toIDs2 = GetDependentComponentIDs(wfDesign, toID);
                        if(toIDs2!=null && toIDs2.Count>0)
                        {
                            foreach(Guid toID2 in toIDs2)
                            {
                                if(!downstreamIDs.Contains(toID2))
                                    downstreamIDs.Add(toID2);
                            }
                        }
                    }
                }
            }
            return downstreamIDs;
        }
        #endregion 

        #region instruction/output
        public static void SaveJobInstruction(InstructionBase instruction)
        {
            JobDALC.SaveJobInstruction(instruction);
        }

        public static InstructionBase GetJobInstructionByJob(Guid jobID)
        {
            string xml = JobDALC.GetJobInstructionByJob(jobID);
            XDocument xDoc = XDocument.Parse(xml);
            Guid componentID = new Guid(xDoc.Root.Attribute("ComponentID").Value);
            ComponentReflector componentReflector = ComponentDALC.GetComponent(componentID);
            WorkflowComponentBase component =
                (WorkflowComponentBase)
                Activator.CreateInstance(ComponentsRegister.ComponentTypes[componentReflector.ComponentTypeName]);
            InstructionBase instruction = component.Instruction.Instantiate(XElement.Parse(xml));
            return instruction;
        }

        public static void SaveJobOutput(ResultBase output)
        {
            JobDALC.SaveJobOutput(output);
        }

        public static ResultBase GetJobOutputByJob(Guid jobID)
        {
            string xml = JobDALC.GetJobOutputByJob(jobID);
            XDocument xDoc = XDocument.Parse(xml);
            Guid componentID = new Guid(xDoc.Root.Attribute("ComponentID").Value);
            ComponentReflector componentReflector = ComponentDALC.GetComponent(componentID);
            WorkflowComponentBase component =
                (WorkflowComponentBase)
                Activator.CreateInstance(ComponentsRegister.ComponentTypes[componentReflector.ComponentTypeName]);
            ResultBase output = component.Output.Instantiate(XElement.Parse(xml));
            return output;
        }

        public static ResultBase GetJobOutputByComponent(Guid componentID, Guid runID)
        {
            Job job = JobDALC.GetJobByComponent(componentID, runID);
            return WorkflowManager.GetJobOutputByJob(job.JobID);
        }

        public static void UpdateJobInstruction(ref InstructionBase instruction,
            ResultBase upstreamJobOutput, CodeFieldMapper[] fieldMappers)
        {
            if (fieldMappers != null && fieldMappers.Length > 0)
            {
                Dictionary<string, Type> writableMembers =
                    instruction.GetBindableMemberNames(null);
                Dictionary<string, Type> readableMembers =
                    upstreamJobOutput.GetBindableMemberNames(null);
                foreach (CodeFieldMapper mapper in fieldMappers)
                {
                    if (writableMembers.ContainsKey(mapper.ToFieldName) &&
                        readableMembers.ContainsKey(mapper.FromFieldName))
                    {
                        object fromValue = null;
                        FieldInfo fromField = upstreamJobOutput.GetType().GetField(mapper.FromFieldName);
                        if (fromField != null)
                            fromValue = fromField.GetValue(upstreamJobOutput);
                        else
                        {
                            PropertyInfo fromProp = upstreamJobOutput.GetType().GetProperty(mapper.FromFieldName);
                            if (fromProp != null)
                                fromValue = fromProp.GetValue(upstreamJobOutput, null);
                        }
                        if (fromValue != null)
                        {
                            if (!string.IsNullOrEmpty(mapper.Code))
                            {
                                MethodArgument arg =
                                    new MethodArgument("fromObj", fromValue.GetType(), false, fromValue);
                                List<MethodArgument> args = new List<MethodArgument>();
                                args.Add(arg);
                                Type returnType = mapper.ToType;
                                MethodConstructInfo methodBuilder =
                                    new MethodConstructInfo(returnType, "Convert", args, mapper.Code);
                                object tmp = CodeDomUtil.ExecuteMethod(methodBuilder);
                                fromValue = tmp;
                            }
                            FieldInfo toField = instruction.GetType().GetField(mapper.ToFieldName);
                            if (toField != null)
                                toField.SetValue(instruction, fromValue);
                            else
                            {
                                PropertyInfo toProp = instruction.GetType().GetProperty(mapper.ToFieldName);
                                if (toProp != null)
                                    toProp.SetValue(instruction, fromValue, null);
                            }
                        }
                    }
                }
            }
        }
        #endregion
    }
}
